package com.swak.utils;

import static java.lang.String.valueOf;
import static java.util.Arrays.asList;
import static org.springframework.core.annotation.AnnotationAttributes.fromMap;
import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;
import static org.springframework.core.annotation.AnnotationUtils.getDefaultValue;
import static org.springframework.util.ClassUtils.resolveClassName;
import static org.springframework.util.CollectionUtils.isEmpty;
import static org.springframework.util.ObjectUtils.nullSafeEquals;
import static org.springframework.util.ReflectionUtils.findMethod;
import static org.springframework.util.ReflectionUtils.invokeMethod;
import static org.springframework.util.StringUtils.trimWhitespace;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;
import org.springframework.util.ClassUtils;

/**
 * {@link Annotation} Utilities
 *
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 * @see Annotation
 * @since 2017.01.13
 */
@SuppressWarnings("unchecked")
public abstract class AnnotationUtils {

	/**
	 * The class name of AnnotatedElementUtils that is introduced since Spring
	 * Framework 4
	 */
	public static final String ANNOTATED_ELEMENT_UTILS_CLASS_NAME = "org.springframework.core.annotation.AnnotatedElementUtils";

	/**
	 * Is specified {@link Annotation} present on {@link Method}'s declaring class
	 * or parameters or itself.
	 *
	 * @param method          {@link Method}
	 * @param annotationClass {@link Annotation} type
	 * @param <A>             {@link Annotation} type
	 * @return If present , return <code>true</code> , or <code>false</code>
	 */
	public static <A extends Annotation> boolean isPresent(Method method, Class<A> annotationClass) {

		Map<ElementType, List<A>> annotationsMap = findAnnotations(method, annotationClass);

		return !annotationsMap.isEmpty();

	}

	/**
	 * Find specified {@link Annotation} type maps from {@link Method}
	 *
	 * @param method          {@link Method}
	 * @param annotationClass {@link Annotation} type
	 * @param <A>             {@link Annotation} type
	 * @return {@link Annotation} type maps , the {@link ElementType} as key , the
	 *         list of {@link Annotation} as value. If {@link Annotation} was
	 *         annotated on {@link Method}'s parameters{@link ElementType#PARAMETER}
	 *         , the associated {@link Annotation} list may contain multiple
	 *         elements.
	 */
	@SuppressWarnings("incomplete-switch")
	public static <A extends Annotation> Map<ElementType, List<A>> findAnnotations(Method method,
			Class<A> annotationClass) {

		Retention retention = annotationClass.getAnnotation(Retention.class);

		RetentionPolicy retentionPolicy = retention.value();

		if (!RetentionPolicy.RUNTIME.equals(retentionPolicy)) {
			return Collections.emptyMap();
		}

		Map<ElementType, List<A>> annotationsMap = new LinkedHashMap<ElementType, List<A>>();

		Target target = annotationClass.getAnnotation(Target.class);

		ElementType[] elementTypes = target.value();

		for (ElementType elementType : elementTypes) {

			List<A> annotationsList = new LinkedList<A>();

			switch (elementType) {

			case PARAMETER:

				Annotation[][] parameterAnnotations = method.getParameterAnnotations();

				for (Annotation[] annotations : parameterAnnotations) {

					for (Annotation annotation : annotations) {

						if (annotationClass.equals(annotation.annotationType())) {

							annotationsList.add((A) annotation);

						}

					}

				}

				break;

			case METHOD:

				A annotation = findAnnotation(method, annotationClass);

				if (annotation != null) {

					annotationsList.add(annotation);

				}

				break;

			case TYPE:

				Class<?> beanType = method.getDeclaringClass();

				A annotation2 = findAnnotation(beanType, annotationClass);

				if (annotation2 != null) {

					annotationsList.add(annotation2);

				}

				break;

			}

			if (!annotationsList.isEmpty()) {

				annotationsMap.put(elementType, annotationsList);

			}

		}

		return Collections.unmodifiableMap(annotationsMap);

	}

	/**
	 * Get the {@link Annotation} attributes
	 *
	 * @param annotation           specified {@link Annotation}
	 * @param ignoreDefaultValue   whether ignore default value or not
	 * @param ignoreAttributeNames the attribute names of annotation should be
	 *                             ignored
	 * @return non-null
	 * @since 1.0.2
	 */
	public static Map<String, Object> getAttributes(Annotation annotation, boolean ignoreDefaultValue,
			String... ignoreAttributeNames) {
		return getAttributes(annotation, null, ignoreDefaultValue, ignoreAttributeNames);
	}

	/**
	 * Get the {@link Annotation} attributes
	 *
	 * @param annotation           specified {@link Annotation}
	 * @param propertyResolver     {@link PropertyResolver} instance, e.g
	 *                             {@link Environment}
	 * @param ignoreDefaultValue   whether ignore default value or not
	 * @param ignoreAttributeNames the attribute names of annotation should be
	 *                             ignored
	 * @return non-null
	 * @since 1.0.2
	 */
	public static Map<String, Object> getAttributes(Annotation annotation, PropertyResolver propertyResolver,
			boolean ignoreDefaultValue, String... ignoreAttributeNames) {
		return getAttributes(annotation, propertyResolver, false, false, ignoreDefaultValue, ignoreAttributeNames);
	}

	/**
	 * Get the {@link Annotation} attributes
	 *
	 * @param annotationAttributes the attributes of specified {@link Annotation}
	 * @param propertyResolver     {@link PropertyResolver} instance, e.g
	 *                             {@link Environment}
	 * @param ignoreAttributeNames the attribute names of annotation should be
	 *                             ignored
	 * @return non-null
	 * @since 1.0.4
	 */
	public static Map<String, Object> getAttributes(Map<String, Object> annotationAttributes,
			PropertyResolver propertyResolver, String... ignoreAttributeNames) {

		Set<String> ignoreAttributeNamesSet = new HashSet<String>(Arrays.asList(ignoreAttributeNames));

		Map<String, Object> actualAttributes = new LinkedHashMap<String, Object>();

		for (Map.Entry<String, Object> annotationAttribute : annotationAttributes.entrySet()) {

			String attributeName = annotationAttribute.getKey();
			Object attributeValue = annotationAttribute.getValue();

			// ignore attribute name
			if (ignoreAttributeNamesSet.contains(attributeName)) {
				continue;
			}

			if (attributeValue instanceof String) {
				attributeValue = resolvePlaceholders(valueOf(attributeValue), propertyResolver);
			} else if (attributeValue instanceof String[]) {
				String[] values = (String[]) attributeValue;
				for (int i = 0; i < values.length; i++) {
					values[i] = resolvePlaceholders(values[i], propertyResolver);
				}
				attributeValue = values;
			}
			actualAttributes.put(attributeName, attributeValue);
		}
		return actualAttributes;
	}

	/**
	 * @param annotation             specified {@link Annotation}
	 * @param propertyResolver       {@link PropertyResolver} instance, e.g
	 *                               {@link Environment}
	 * @param classValuesAsString    whether to turn Class references into Strings
	 *                               (for compatibility with
	 *                               {@link org.springframework.core.type.AnnotationMetadata}
	 *                               or to preserve them as Class references
	 * @param nestedAnnotationsAsMap whether to turn nested Annotation instances
	 *                               into {@link AnnotationAttributes} maps (for
	 *                               compatibility with
	 *                               {@link org.springframework.core.type.AnnotationMetadata}
	 *                               or to preserve them as Annotation instances
	 * @param ignoreDefaultValue     whether ignore default value or not
	 * @param ignoreAttributeNames   the attribute names of annotation should be
	 *                               ignored
	 * @return
	 * @since 1.0.11
	 */
	public static Map<String, Object> getAttributes(Annotation annotation, PropertyResolver propertyResolver,
			boolean classValuesAsString, boolean nestedAnnotationsAsMap, boolean ignoreDefaultValue,
			String... ignoreAttributeNames) {

		Map<String, Object> annotationAttributes = org.springframework.core.annotation.AnnotationUtils
				.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap);

		String[] actualIgnoreAttributeNames = ignoreAttributeNames;

		if (ignoreDefaultValue && !isEmpty(annotationAttributes)) {

			List<String> attributeNamesToIgnore = new LinkedList<String>(asList(ignoreAttributeNames));

			for (Map.Entry<String, Object> annotationAttribute : annotationAttributes.entrySet()) {
				String attributeName = annotationAttribute.getKey();
				Object attributeValue = annotationAttribute.getValue();
				if (nullSafeEquals(attributeValue, getDefaultValue(annotation, attributeName))) {
					attributeNamesToIgnore.add(attributeName);
				}
			}
			// extends the ignored list
			actualIgnoreAttributeNames = attributeNamesToIgnore.toArray(new String[attributeNamesToIgnore.size()]);
		}

		return getAttributes(annotationAttributes, propertyResolver, actualIgnoreAttributeNames);
	}

	private static String resolvePlaceholders(String attributeValue, PropertyResolver propertyResolver) {
		String resolvedValue = attributeValue;
		if (propertyResolver != null) {
			resolvedValue = propertyResolver.resolvePlaceholders(resolvedValue);
			resolvedValue = trimWhitespace(resolvedValue);
		}
		return resolvedValue;
	}

	/**
	 * Get the attribute value
	 *
	 * @param annotation    {@link Annotation annotation}
	 * @param attributeName the name of attribute
	 * @param <T>           the type of attribute value
	 * @return the attribute value if found
	 * @since 1.0.3
	 */
	public static <T> T getAttribute(Annotation annotation, String attributeName) {
		return getAttribute(org.springframework.core.annotation.AnnotationUtils.getAnnotationAttributes(annotation),
				attributeName);
	}

	/**
	 * Get the attribute value
	 *
	 * @param attributes    {@link Map the annotation attributes} or
	 *                      {@link AnnotationAttributes}
	 * @param attributeName the name of attribute
	 * @param <T>           the type of attribute value
	 * @return the attribute value if found
	 * @since 1.0.3
	 */
	public static <T> T getAttribute(Map<String, Object> attributes, String attributeName) {
		return getAttribute(attributes, attributeName, false);
	}

	/**
	 * Get the attribute value the will
	 *
	 * @param attributes    {@link Map the annotation attributes} or
	 *                      {@link AnnotationAttributes}
	 * @param attributeName the name of attribute
	 * @param required      the required attribute or not
	 * @param <T>           the type of attribute value
	 * @return the attribute value if found
	 * @throws IllegalStateException if attribute value can't be found
	 * @since 1.0.6
	 */
	public static <T> T getAttribute(Map<String, Object> attributes, String attributeName, boolean required) {
		T value = getAttribute(attributes, attributeName, null);
		if (required && value == null) {
			throw new IllegalStateException("The attribute['" + attributeName + "] is required!");
		}
		return value;
	}

	/**
	 * Get the attribute value with default value
	 *
	 * @param attributes    {@link Map the annotation attributes} or
	 *                      {@link AnnotationAttributes}
	 * @param attributeName the name of attribute
	 * @param defaultValue  the default value of attribute
	 * @param <T>           the type of attribute value
	 * @return the attribute value if found
	 * @since 1.0.6
	 */
	public static <T> T getAttribute(Map<String, Object> attributes, String attributeName, T defaultValue) {
		T value = (T) attributes.get(attributeName);
		return value == null ? defaultValue : value;
	}

	/**
	 * Get the required attribute value
	 *
	 * @param attributes    {@link Map the annotation attributes} or
	 *                      {@link AnnotationAttributes}
	 * @param attributeName the name of attribute
	 * @param <T>           the type of attribute value
	 * @return the attribute value if found
	 * @throws IllegalStateException if attribute value can't be found
	 * @since 1.0.6
	 */
	public static <T> T getRequiredAttribute(Map<String, Object> attributes, String attributeName) {
		return getAttribute(attributes, attributeName, true);
	}

	/**
	 * Get the {@link AnnotationAttributes}
	 *
	 * @param annotation           specified {@link Annotation}
	 * @param ignoreDefaultValue   whether ignore default value or not
	 * @param ignoreAttributeNames the attribute names of annotation should be
	 *                             ignored
	 * @return non-null
	 * @see #getAnnotationAttributes(Annotation, PropertyResolver, boolean,
	 *      String...)
	 * @since 1.0.3
	 */
	public static AnnotationAttributes getAnnotationAttributes(Annotation annotation, boolean ignoreDefaultValue,
			String... ignoreAttributeNames) {
		return getAnnotationAttributes(annotation, null, ignoreDefaultValue, ignoreAttributeNames);
	}

	/**
	 * Get the {@link AnnotationAttributes}
	 *
	 * @param annotation             specified {@link Annotation}
	 * @param propertyResolver       {@link PropertyResolver} instance, e.g
	 *                               {@link Environment}
	 * @param classValuesAsString    whether to turn Class references into Strings
	 *                               (for compatibility with
	 *                               {@link org.springframework.core.type.AnnotationMetadata}
	 *                               or to preserve them as Class references
	 * @param nestedAnnotationsAsMap whether to turn nested Annotation instances
	 *                               into {@link AnnotationAttributes} maps (for
	 *                               compatibility with
	 *                               {@link org.springframework.core.type.AnnotationMetadata}
	 *                               or to preserve them as Annotation instances
	 * @param ignoreAttributeNames   the attribute names of annotation should be
	 *                               ignored
	 * @param ignoreDefaultValue     whether ignore default value or not
	 * @return non-null
	 * @see #getAttributes(Annotation, PropertyResolver, boolean, String...)
	 * @see #getAnnotationAttributes(AnnotatedElement, Class, PropertyResolver,
	 *      boolean, String...)
	 * @since 1.0.11
	 */
	public static AnnotationAttributes getAnnotationAttributes(Annotation annotation, PropertyResolver propertyResolver,
			boolean classValuesAsString, boolean nestedAnnotationsAsMap, boolean ignoreDefaultValue,
			String... ignoreAttributeNames) {
		return fromMap(getAttributes(annotation, propertyResolver, classValuesAsString, nestedAnnotationsAsMap,
				ignoreDefaultValue, ignoreAttributeNames));
	}

	/**
	 * Get the {@link AnnotationAttributes}
	 *
	 * @param annotation           specified {@link Annotation}
	 * @param propertyResolver     {@link PropertyResolver} instance, e.g
	 *                             {@link Environment}
	 * @param ignoreDefaultValue   whether ignore default value or not
	 * @param ignoreAttributeNames the attribute names of annotation should be
	 *                             ignored
	 * @return non-null
	 * @see #getAttributes(Annotation, PropertyResolver, boolean, String...)
	 * @see #getAnnotationAttributes(AnnotatedElement, Class, PropertyResolver,
	 *      boolean, String...)
	 * @since 1.0.3
	 */
	public static AnnotationAttributes getAnnotationAttributes(Annotation annotation, PropertyResolver propertyResolver,
			boolean ignoreDefaultValue, String... ignoreAttributeNames) {
		return getAnnotationAttributes(annotation, propertyResolver, false, false, ignoreDefaultValue,
				ignoreAttributeNames);
	}

	/**
	 * Get the {@link AnnotationAttributes}
	 *
	 * @param annotatedElement     {@link AnnotatedElement the annotated element}
	 * @param annotationType       the {@link Class tyoe} pf {@link Annotation
	 *                             annotation}
	 * @param propertyResolver     {@link PropertyResolver} instance, e.g
	 *                             {@link Environment}
	 * @param ignoreDefaultValue   whether ignore default value or not
	 * @param ignoreAttributeNames the attribute names of annotation should be
	 *                             ignored
	 * @return if <code>annotatedElement</code> can't be found in
	 *         <code>annotatedElement</code>, return <code>null</code>
	 * @since 1.0.3
	 */
	public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
			Class<? extends Annotation> annotationType, PropertyResolver propertyResolver, boolean ignoreDefaultValue,
			String... ignoreAttributeNames) {
		return getAnnotationAttributes(annotatedElement, annotationType, propertyResolver, false, false,
				ignoreDefaultValue, ignoreAttributeNames);
	}

	/**
	 * Get the {@link AnnotationAttributes}
	 *
	 * @param annotatedElement     {@link AnnotatedElement the annotated element}
	 * @param annotationType       the {@link Class tyoe} pf {@link Annotation
	 *                             annotation}
	 * @param propertyResolver     {@link PropertyResolver} instance, e.g
	 *                             {@link Environment}
	 * @param ignoreDefaultValue   whether ignore default value or not
	 * @param ignoreAttributeNames the attribute names of annotation should be
	 *                             ignored
	 * @return if <code>annotatedElement</code> can't be found in
	 *         <code>annotatedElement</code>, return <code>null</code>
	 * @since 1.0.11
	 */
	public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
			Class<? extends Annotation> annotationType, PropertyResolver propertyResolver, boolean classValuesAsString,
			boolean nestedAnnotationsAsMap, boolean ignoreDefaultValue, String... ignoreAttributeNames) {
		Annotation annotation = annotatedElement.getAnnotation(annotationType);
		return annotation == null ? null
				: getAnnotationAttributes(annotation, propertyResolver, classValuesAsString, nestedAnnotationsAsMap,
						ignoreDefaultValue, ignoreAttributeNames);
	}

	/**
	 * Get the {@link AnnotationAttributes}, if the argument
	 * <code>tryMergedAnnotation</code> is <code>true</code>, the
	 * {@link AnnotationAttributes} will be got from
	 * {@link #tryGetMergedAnnotationAttributes(AnnotatedElement, Class, PropertyResolver, boolean, String...)
	 * merged annotation} first, if failed, and then to get from
	 * {@link #getAnnotationAttributes(AnnotatedElement, Class, PropertyResolver, boolean, boolean, String...)
	 * normal one}
	 *
	 * @param annotatedElement     {@link AnnotatedElement the annotated element}
	 * @param annotationType       the {@link Class tyoe} pf {@link Annotation
	 *                             annotation}
	 * @param propertyResolver     {@link PropertyResolver} instance, e.g
	 *                             {@link Environment}
	 * @param ignoreDefaultValue   whether ignore default value or not
	 * @param tryMergedAnnotation  whether try merged annotation or not
	 * @param ignoreAttributeNames the attribute names of annotation should be
	 *                             ignored
	 * @return if <code>annotatedElement</code> can't be found in
	 *         <code>annotatedElement</code>, return <code>null</code>
	 * @since 1.0.3
	 */
	public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
			Class<? extends Annotation> annotationType, PropertyResolver propertyResolver, boolean ignoreDefaultValue,
			boolean tryMergedAnnotation, String... ignoreAttributeNames) {
		return getAnnotationAttributes(annotatedElement, annotationType, propertyResolver, false, false,
				ignoreDefaultValue, tryMergedAnnotation, ignoreAttributeNames);
	}

	/**
	 * Get the {@link AnnotationAttributes}, if the argument
	 * <code>tryMergedAnnotation</code> is <code>true</code>, the
	 * {@link AnnotationAttributes} will be got from
	 * {@link #tryGetMergedAnnotationAttributes(AnnotatedElement, Class, PropertyResolver, boolean, String...)
	 * merged annotation} first, if failed, and then to get from
	 * {@link #getAnnotationAttributes(AnnotatedElement, Class, PropertyResolver, boolean, boolean, String...)
	 * normal one}
	 *
	 * @param annotatedElement       {@link AnnotatedElement the annotated element}
	 * @param annotationType         the {@link Class tyoe} pf {@link Annotation
	 *                               annotation}
	 * @param propertyResolver       {@link PropertyResolver} instance, e.g
	 *                               {@link Environment}
	 * @param classValuesAsString    whether to turn Class references into Strings
	 *                               (for compatibility with
	 *                               {@link org.springframework.core.type.AnnotationMetadata}
	 *                               or to preserve them as Class references
	 * @param nestedAnnotationsAsMap whether to turn nested Annotation instances
	 *                               into {@link AnnotationAttributes} maps (for
	 *                               compatibility with
	 *                               {@link org.springframework.core.type.AnnotationMetadata}
	 *                               or to preserve them as Annotation instances
	 * @param ignoreDefaultValue     whether ignore default value or not
	 * @param tryMergedAnnotation    whether try merged annotation or not
	 * @param ignoreAttributeNames   the attribute names of annotation should be
	 *                               ignored
	 * @return if <code>annotatedElement</code> can't be found in
	 *         <code>annotatedElement</code>, return <code>null</code>
	 * @since 1.0.11
	 */
	public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
			Class<? extends Annotation> annotationType, PropertyResolver propertyResolver, boolean classValuesAsString,
			boolean nestedAnnotationsAsMap, boolean ignoreDefaultValue, boolean tryMergedAnnotation,
			String... ignoreAttributeNames) {

		AnnotationAttributes attributes = null;

		if (tryMergedAnnotation) {
			attributes = tryGetMergedAnnotationAttributes(annotatedElement, annotationType, propertyResolver,
					classValuesAsString, nestedAnnotationsAsMap, ignoreDefaultValue, ignoreAttributeNames);
		}

		if (attributes == null) {
			attributes = getAnnotationAttributes(annotatedElement, annotationType, propertyResolver,
					classValuesAsString, nestedAnnotationsAsMap, ignoreDefaultValue, ignoreAttributeNames);
		}

		return attributes;
	}

	/**
	 * Try to get the merged {@link Annotation annotation}
	 *
	 * @param annotatedElement {@link AnnotatedElement the annotated element}
	 * @param annotationType   the {@link Class tyoe} pf {@link Annotation
	 *                         annotation}
	 * @return If current version of Spring Framework is below 4.2, return
	 *         <code>null</code>
	 * @since 1.0.3
	 */
	public static Annotation tryGetMergedAnnotation(AnnotatedElement annotatedElement,
			Class<? extends Annotation> annotationType) {
		return tryGetMergedAnnotation(annotatedElement, annotationType, false, false);
	}

	/**
	 * Try to get the merged {@link Annotation annotation}
	 *
	 * @param annotatedElement       {@link AnnotatedElement the annotated element}
	 * @param annotationType         the {@link Class tyoe} pf {@link Annotation
	 *                               annotation}
	 * @param classValuesAsString    whether to turn Class references into Strings
	 *                               (for compatibility with
	 *                               {@link org.springframework.core.type.AnnotationMetadata}
	 *                               or to preserve them as Class references
	 * @param nestedAnnotationsAsMap whether to turn nested Annotation instances
	 *                               into {@link AnnotationAttributes} maps (for
	 *                               compatibility with
	 *                               {@link org.springframework.core.type.AnnotationMetadata}
	 *                               or to preserve them as Annotation instances
	 * @return If current version of Spring Framework is below 4.2, return
	 *         <code>null</code>
	 * @since 1.0.11
	 */
	public static Annotation tryGetMergedAnnotation(AnnotatedElement annotatedElement,
			Class<? extends Annotation> annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {

		Annotation mergedAnnotation = null;

		ClassLoader classLoader = annotationType.getClassLoader();

		if (ClassUtils.isPresent(ANNOTATED_ELEMENT_UTILS_CLASS_NAME, classLoader)) {
			Class<?> annotatedElementUtilsClass = resolveClassName(ANNOTATED_ELEMENT_UTILS_CLASS_NAME, classLoader);
			// getMergedAnnotation method appears in the Spring Framework 4.2
			Method getMergedAnnotationMethod = findMethod(annotatedElementUtilsClass, "getMergedAnnotation",
					AnnotatedElement.class, Class.class, boolean.class, boolean.class);
			if (getMergedAnnotationMethod != null) {
				mergedAnnotation = (Annotation) invokeMethod(getMergedAnnotationMethod, null, annotatedElement,
						annotationType, classValuesAsString, nestedAnnotationsAsMap);
			}
		}

		return mergedAnnotation;
	}

	/**
	 * Try to get {@link AnnotationAttributes the annotation attributes} after
	 * merging and resolving the placeholders
	 *
	 * @param annotatedElement     {@link AnnotatedElement the annotated element}
	 * @param annotationType       the {@link Class tyoe} pf {@link Annotation
	 *                             annotation}
	 * @param propertyResolver     {@link PropertyResolver} instance, e.g
	 *                             {@link Environment}
	 * @param ignoreDefaultValue   whether ignore default value or not
	 * @param ignoreAttributeNames the attribute names of annotation should be
	 *                             ignored
	 * @return If the specified annotation type is not found, return
	 *         <code>null</code>
	 * @since 1.0.3
	 */
	public static AnnotationAttributes tryGetMergedAnnotationAttributes(AnnotatedElement annotatedElement,
			Class<? extends Annotation> annotationType, PropertyResolver propertyResolver, boolean ignoreDefaultValue,
			String... ignoreAttributeNames) {
		return tryGetMergedAnnotationAttributes(annotatedElement, annotationType, propertyResolver, false, false,
				ignoreDefaultValue, ignoreAttributeNames);
	}

	/**
	 * Try to get {@link AnnotationAttributes the annotation attributes} after
	 * merging and resolving the placeholders
	 *
	 * @param annotatedElement       {@link AnnotatedElement the annotated element}
	 * @param annotationType         the {@link Class tyoe} pf {@link Annotation
	 *                               annotation}
	 * @param propertyResolver       {@link PropertyResolver} instance, e.g
	 *                               {@link Environment}
	 * @param classValuesAsString    whether to turn Class references into Strings
	 *                               (for compatibility with
	 *                               {@link org.springframework.core.type.AnnotationMetadata}
	 *                               or to preserve them as Class references
	 * @param nestedAnnotationsAsMap whether to turn nested Annotation instances
	 *                               into {@link AnnotationAttributes} maps (for
	 *                               compatibility with
	 *                               {@link org.springframework.core.type.AnnotationMetadata}
	 *                               or to preserve them as Annotation instances
	 * @param ignoreDefaultValue     whether ignore default value or not
	 * @param ignoreAttributeNames   the attribute names of annotation should be
	 *                               ignored
	 * @return If the specified annotation type is not found, return
	 *         <code>null</code>
	 * @since 1.0.11
	 */
	public static AnnotationAttributes tryGetMergedAnnotationAttributes(AnnotatedElement annotatedElement,
			Class<? extends Annotation> annotationType, PropertyResolver propertyResolver, boolean classValuesAsString,
			boolean nestedAnnotationsAsMap, boolean ignoreDefaultValue, String... ignoreAttributeNames) {
		Annotation annotation = tryGetMergedAnnotation(annotatedElement, annotationType, classValuesAsString,
				nestedAnnotationsAsMap);
		return annotation == null ? null
				: getAnnotationAttributes(annotation, propertyResolver, classValuesAsString, nestedAnnotationsAsMap,
						ignoreDefaultValue, ignoreAttributeNames);
	}

}
